package org.videopractice.network.handler;

import com.google.gson.Gson;

import org.videopractice.common.ClassUtils;
import org.videopractice.common.Logger;
import org.videopractice.network.ChannelManager;
import org.videopractice.network.ClientServer;
import org.videopractice.network.IoSession;
import org.videopractice.network.ServerManager;
import org.videopractice.network.message.BaseDto;
import org.videopractice.network.message.GUID;
import org.videopractice.network.message.Message;
import org.videopractice.network.message.MessageCode;
import org.videopractice.network.message.request.HeartBeatRequest;

import java.io.IOException;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * 客户端消息处理器
 */
public class ClientMessageHandler extends SimpleChannelInboundHandler<Message> {

    private ServerManager serverManager = new ServerManager();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Logger.d("ClientMessageHandler ============= 链接激活了");
        if (!ChannelManager.addSession(ctx.channel(), new IoSession(ctx.channel()))) {
            ctx.channel().close();
            Logger.e("链接已存在：channelId: " + ctx.channel().id());
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Logger.d("ClientMessageHandler ============= 链接失效了");
        logout(ctx.channel(), ctx);	// 异常登出
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Logger.d("ClientMessageHandler ============= 发生了异常意外");
        if (cause instanceof IOException) {	// IO异常直接登出，关闭链接
            logout(ctx.channel(), ctx);	// 异常登出
        } else {	// 业务异常已经在process方法里处理
            Logger.i("链接业务异常");
            cause.printStackTrace();
        }
    }

    /**
     * 登出注销
     * @param channel
     * @param ctx
     */
    private void logout(Channel channel, ChannelHandlerContext ctx) {
        serverManager.logout(channel);	// 注销通道，包括session
        if(channel.isActive()) {
            ctx.close();
        }
        ClientServer.SERVER.close();
    }

    /**
     * 心跳事件：清除多余链接
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            Channel channel = ctx.channel();
            switch (e.state()) {
                case READER_IDLE:	// 客户端读超时
                case WRITER_IDLE:	// 客户端写超时
                case ALL_IDLE:
                    // 闲置就发起心跳包
                    HeartBeatRequest request = new HeartBeatRequest();
                    request.setTimestamp(System.currentTimeMillis());
                    Message message = new Message(MessageCode.HeartBeatRequest, GUID.generateByUUID(), new Gson().toJson(request));
                    serverManager.sendMessage(channel, message);
                    break;
            }
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
        Logger.d("ClientMessageHandler ============= 接受到了消息并 : " + msg.toString());

        final Channel channel = ctx.channel();

        // 前置处理
        if(!preProcess(msg, channel)) {
            return;
        }

        // 业务处理（XXX 高并发情况下，应使用线程池异步处理业务逻辑）
        Message responseMessage = this.process(msg, channel);
        // 向客户端发送消息
        if(responseMessage != null) {
            serverManager.sendMessage(ctx.channel(), responseMessage);
        }
    }

    private boolean preProcess(Message requestMessage, Channel channel){
        // 报文为空
        if(requestMessage == null) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    private Message process(Message msg, Channel channel){
        BaseDto request = null;
        if(msg.getCode().isJsonCoverToObject()) {	// 将消息体从JSON格式字符串转换为指定对象
            Class requestObjectClass = msg.getCode().getMessageClass();
            request = (BaseDto) new Gson().fromJson(msg.getBody(), requestObjectClass);
        }
        Class<? extends IService> serviceClass = MessageCode.getServiceClassByCode(msg.getCode());
        if(serviceClass == null) {
            return null;
        }
        IService process = ClassUtils.newInstance(serviceClass);
        return process.process(request, msg, channel);
    }

}
